package com.abel.bigwater.data.jnrq

import com.abel.bigwater.PacketHelper.hexString
import com.abel.bigwater.data.BcdString
import com.abel.bigwater.data.MsgHelper
import com.abel.bigwater.data.mapper.DataMapper
import com.abel.bigwater.data.mapper.DeviceTemplate
import com.abel.bigwater.data.mapper.RtuMapper
import com.abel.bigwater.model.BwData
import com.abel.bigwater.model.BwRtu
import com.abel.bigwater.model.BwRtuLog
import com.abel.bigwater.msg.RtuMsg
import com.abel.bigwater.msg.RtuMsgType
import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.serializer.SerializerFeature
import com.fasterxml.jackson.databind.ObjectMapper
import io.netty.channel.ChannelHandler
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInboundHandlerAdapter
import io.netty.channel.socket.DatagramPacket
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import org.joda.time.DateTime
import org.slf4j.LoggerFactory
import java.io.File
import java.io.FileReader
import java.net.InetAddress
import java.net.InetSocketAddress
import java.util.*
import kotlin.collections.ArrayList

class JnrqTcpHandler(
        val dataMapper: DataMapper,
        val rtuMapper: RtuMapper,
        val _firmId: String,
        val _firmCode: String,
        val msgHelper: MsgHelper,
        val devTemplate: DeviceTemplate) : ChannelInboundHandlerAdapter() {

    /*
    init {
        var rtuList: RtuMeterCollection? = null

        FileReader(listJsonFile).also {
            try {
                rtuList = ObjectMapper().readValue(it.readText(), RtuMeterCollection::class.java)
                rtuList?.rtuMeterList?.forEach {
                    it.meterAddrList?.forEach { assert(it.length == 7 * 2 || it.length == 8 * 2) }
                }

                lgr.info("rtu-meter-list:\n${JSON.toJSONString(rtuList, true).take(200)}...")
            } finally {
                it.close()
            }
        }
    }
    */

    /**
     * 检查是否需要下载
     */
    fun checkDownload(rtuId: String): RtuMeterCollection? {
        val fname = "${rtuId}.json"
        if (!File(fname).exists()) return null

        var rd: FileReader? = null
        try {
            rd = FileReader(fname)
            val rtuList = ObjectMapper().readValue(rd.readText(), RtuMeterCollection::class.java)

            // 修改名称
            rd.close()
            File(fname).renameTo(File(fname + "." + DateTime.now().toString("yyyyMMdd'T'HHmmss")))

            return rtuList
        } catch (ex: Exception) {
            lgr.error("解析地址下载文件失败： ${fname}, ${ex.message}", ex)
        } finally {
            try {
                rd?.close()
            } catch (ex: Exception) {
            }
        }

        return null
    }

    /**
     * Calls [ChannelHandlerContext.fireChannelRead] to forward
     * to the next [ChannelInboundHandler] in the [ChannelPipeline].
     *
     * Sub-classes may override this method to change behavior.
     */
    override fun channelRead(ctx: ChannelHandlerContext?, msg: Any?) {
        val p = if (msg is DatagramPacket) {
            val hexstr = msg.content().hexString(null, true)
            lgr.info("got UDP: {}", hexstr)
            val p1 = JnrqPacket.splitPacket(msg.content())

            if (p1 != null) {
                msgHelper.sendRawPacket(RtuMsg().apply {
                    station = p1.devAddrInt?.toString()
                    rawBinary = p1.rawBinary
                    msgType = RtuMsgType.DATA_BIN.name
                    rawHex = BcdString.hexString(p1.rawBinary!!)

                    source = ctx!!.channel().remoteAddress().toString()
                    target = ctx.channel().localAddress().toString()
                    serverPort = (ctx.channel().localAddress() as InetSocketAddress?)?.port

                    firmId = _firmId
                    firmCode = _firmCode
                    recvTime = Date()
                })
            } else lgr.warn("invalid jnrq-packet from udp: {}...", hexstr.take(98))

            if (p1 != null) JnrqPacket.parseBuffer(msg.content()) else null
        } else msg as JnrqPacket?

        if (p == null) {
            lgr.warn("unknown packet: {}", msg)
            return
        }

        kotlin.run {
            // 查找rtu-meter-addr
            val rtuMeter = checkDownload("${p.devAddrInt}")?.rtuMeterList?.find { it.rtuAddrInt == p.devAddrInt }
            lgr.warn("准备下发指令: ${JSON.toJSONStringWithDateFormat(rtuMeter, JSON.DEFFAULT_DATE_FORMAT, SerializerFeature.PrettyFormat)}")

            if (rtuMeter != null) {
                if (rtuMeter.meterAddrList?.isNotEmpty() == true) {
                    downloadMeterAddress(rtuMeter, p, ctx)

                    Thread.sleep(10)
                }

                // 3、修改 IP 地址，下行报文（服务器→集中器）
                if (!rtuMeter.ipStr.isNullOrBlank() && (rtuMeter.port ?: 0) > 0) {
                    downloadUpdateIpPort(p, rtuMeter, ctx!!)

                    Thread.sleep(10)
                }

                // send timing packet.
                downloadTimingData(p, ctx)
                Thread.sleep(10)
            }

            when (p.packetType) {
                PacketType.UploadHistoryEmpty -> {
                    if (p.data is UploadEmptyData) {
                        // save rtu-log and update rtu status
                        GlobalScope.async {
                            try {
                                storeEmptyData(ctx, p)
                            } catch (ex: Exception) {
                                val logstr = "保存消息异常: ${ex.message}"
                                lgr.error(logstr, ex)

                                msgHelper.sendHandleTopic(RtuMsg().apply {
                                    station = p.devAddrInt?.toString()
                                    msgType = RtuMsgType.STATUS.name
                                    text = logstr
                                })
                            }
                        }
                    } else {
                        // 历史数据
                        GlobalScope.async {
                            try {
                                storeHistoryData(ctx, p)
                            } catch (ex: Exception) {
                                val logstr = "保存消息异常: ${ex.message}, ${JSON.toJSONString(p).take(200)}..."
                                lgr.error(logstr, ex)

                                msgHelper.sendHandleTopic(RtuMsg().apply {
                                    station = p.devAddrInt?.toString()
                                    msgType = RtuMsgType.STATUS.name
                                    text = logstr
                                })
                            }
                        }
                    }

                    // send timing packet.
                    downloadTimingData(p, ctx)
                    Thread.sleep(10)
                }

                PacketType.AnswerDownloadAddr -> {
                    val download = p.data as JnrqDownloadAddrReply
                    lgr.info("下载地址结果：${p.devAddrInt} - ${download.downloadState}")
                    /*
                    if (download.downloadState == 1.toShort()) {
                        rtuList!!.rtuMeterList!!.find { it.rtuAddrInt == msg.devAddrInt }?.downloaded = true
                    }
                    */
                }
            }
        }
    }

    /**
     * 下发校时包，下载地址或收到数据后下发.
     */
    private fun downloadTimingData(msg: JnrqPacket, ctx: ChannelHandlerContext?) {
        val r1 = JnrqPacket().apply {
            packetType = PacketType.AdjustTime
            devAddrInt = msg.devAddrInt
            data = TimingData()
        }

        // check device
        val dev = devTemplate.findJnrqDevice(msg.devAddrInt?.toString().orEmpty())
        if (dev != null) {
            (r1.data as TimingData).apply {
                readPeriodStr = byteArrayOf(
                        dev.readPeriodObj.seconds.toByte(),
                        dev.readPeriodObj.minutes.toByte(),
                        dev.readPeriodObj.hours.toByte(),
                        dev.readPeriodObj.days.toByte())

                uploadPeriodStr = byteArrayOf(
                        dev.uploadPeriodObj.seconds.toByte(),
                        dev.uploadPeriodObj.minutes.toByte(),
                        dev.uploadPeriodObj.hours.toByte(),
                        dev.uploadPeriodObj.days.toByte())
            }

            lgr.warn("change timing data: {}", JSON.toJSONString(dev))
        }

        // 系统设置
        (r1.data as TimingData).apply {
            serverTime = DateTime.now()
        }

        val buf = ctx!!.alloc().buffer(1024)
        r1.fillBuffer(buf)
        val bstr2 = BcdString.create(buf.duplicate(), buf.readableBytes())
        ctx.writeAndFlush(buf)

        lgr.info("下发校时包:\n${bstr2.bcdStringBlank()}")
    }

    /**
     * 下载地址包.
     */
    private fun downloadMeterAddress(rtuMeter: RtuMeter, msg: JnrqPacket, ctx: ChannelHandlerContext?) {
        val download = DownloadAddrData().also {
            it.meterList = ArrayList()
            it.totalPackageCount = 1
            it.packageIndex = 1
            it.meterCountInPackage = rtuMeter.meterAddrList!!.count()
        }

        rtuMeter.meterAddrStrList!!.forEach {
            download.meterList!!.add(
                    if (it.size == 8) it
                    else BcdString.insertBefore(it, JnrqPacket.BIG_METER_FLAG)
            )
        }

        val addrDownloadPacket = JnrqPacket().apply {
            packetType = PacketType.DownloadAddress
            devAddrInt = msg.devAddrInt

            data = download
        }

        // send out
        val tmp = ctx!!.alloc().buffer(1024)
        addrDownloadPacket.fillBuffer(tmp)
        val bstr = BcdString.create(tmp.duplicate(), tmp.readableBytes())
        ctx.writeAndFlush(tmp)

        lgr.info("下发地址包:\n${bstr.bcdStringBlank()}")
    }

    /**
     * 下发切换服务器:端口包.
     */
    private fun downloadUpdateIpPort(msg: JnrqPacket, rtuMeter: RtuMeter, ctx: ChannelHandlerContext) {
        val reply = JnrqPacket().apply {
            packetType = PacketType.UpdateIp
            devAddrInt = msg.devAddrInt

            data = UpdateIpData().also {
                it.streamType =
                        if (rtuMeter.streamTypeId != null)
                            JnrqStreamType.findByCode(rtuMeter.streamTypeId!!) ?: JnrqStreamType.TCP
                        else JnrqStreamType.TCP
                it.ip = InetAddress.getByName(rtuMeter.ipStr)
                it.port = rtuMeter.port
            }
        }

        val buf = ctx.alloc().buffer(1024).apply {
            reply.fillBuffer(this)
            writeBytes(this)
        }

        lgr.info("修改 IP 地址，下行报文（服务器→集中器）\n${BcdString.hexString(buf, buf.readableBytes(), true)}")
        ctx.writeAndFlush(buf)
    }

    /**
     * 保存握手包
     * update rtu-status and rtu-log.
     */
    private fun storeEmptyData(ctx: ChannelHandlerContext?, msg: JnrqPacket) {
        val hist = msg.data as UploadEmptyData
        val meterId = "${_firmCode}${msg.devAddrInt}"
        lgr.info("正在保存瑞泉握手包 ${meterId} (${hist.swVersion}) ...")

        // 更新rtu状态
        val rtu = BwRtu().also {
            it.rtuId = meterId
            it.meterId = meterId
            it.firmId = _firmId

            it.lastText = "${meterId}: ${JSON.toJSONString(msg)}".take(200)
            it.lastCmd = "JnrqEmpty"
            it.remoteServer = ctx?.channel()?.remoteAddress().toString()

            it.lastDataTime = Date()

            it.rssi = hist.rssi ?: -1
        }

        if (1 > rtuMapper.updateRtu(rtu) ?: 0) {
            rtuMapper.addRtu(rtu)
        }

        // store rtu-log
        val rlog = BwRtuLog().also {
            it.firmId = _firmId
            it.rtuId = meterId
            it.meterId = meterId

            it.remoteServer = ctx?.channel()?.remoteAddress().toString()

            it.logTime = Date()
            it.logCmd = "JnrqHist"
            it.logText = "${JSON.toJSONString(msg)}".take(200)
            it.rssi = hist.rssi ?: -1

            it.createDate = Date()
        }

        rtuMapper.addRtuLog(rlog)

        lgr.info("保存瑞泉握手包成功！")
    }

    /**
     * 保存历史数据
     */
    internal fun storeHistoryData(ctx: ChannelHandlerContext?, msg: JnrqPacket) {
        val hist = msg.data as UploadHistoryData
        val rtuId = "${_firmCode}${msg.devAddrInt}"
        lgr.info("正在保存瑞泉历史数据 ${rtuId} (${hist.aiNo}) ${hist.waterList?.size} 条...")

        // 先保存数据
        var meterId: String? = null
        for (wd in hist.waterList.orEmpty()) {
            // ignore same meter-code but different rtu-address.
            val m1 = devTemplate.findJnrqDeviceByMeter(wd.meterAddr.orEmpty())
            meterId = "${_firmCode}${wd.meterAddr}"

            if (m1 != null && m1.address?.toIntOrNull() != msg.devAddrInt) {
                lgr.warn("ignore duplicate meter-code with different rtu-address: {}/{}, should be {}/{}",
                        msg.devAddrInt, wd.meterAddr, m1.address, m1.meterCode)
                continue
            }

            val data = BwData().also {
                it.meterId = meterId
                it.extId = meterId
                it.firmId = _firmId
                it.sampleTime = wd.sampleTime
                it.literPulse = 1000
                it.forwardDigits = wd.waterDigits
            }

            if (1 > dataMapper.updateRealtime(data) ?: 0) {
                dataMapper.insertRealtime(data)
            }
        }

        // 更新rtu状态
        val last = hist.waterList?.lastOrNull()
        val rtu = BwRtu().also {
            it.rtuId = rtuId
            it.meterId = meterId
            it.firmId = _firmId

            it.lastText = "${meterId}: ${JSON.toJSONString(msg)}".take(200)
            it.lastCmd = "JnrqHist"
            it.remoteServer = ctx?.channel()?.remoteAddress().toString()

            it.lastDataTime = Date()
            it.literPulse = 1000
            it.forwardReading = last?.waterDigits

            it.stateDesc = last?.readStatus?.name
            it.rssi = hist.rssi ?: -1
            it.voltWarn = hist.volt ?: -1
        }

        if (1 > rtuMapper.updateRtu(rtu) ?: 0) {
            rtuMapper.addRtu(rtu)
        }

        // store rtu-log
        val rlog = BwRtuLog().also {
            it.firmId = _firmId
            it.rtuId = rtuId
            it.meterId = meterId

            it.literPulse = 1000
            it.forwardReading = last?.waterDigits
            it.remoteServer = ctx?.channel()?.remoteAddress().toString()

            it.logTime = Date()
            it.logCmd = "JnrqHist"
            it.logText = "${JSON.toJSONString(msg)}".take(200)
            it.rssi = hist.rssi ?: -1
            it.voltWarn = hist.volt ?: -1

            it.createDate = Date()
        }

        rtuMapper.addRtuLog(rlog)

        lgr.info("保存历史数据包成功.")
    }

    /**
     * Calls [ChannelHandlerContext.fireChannelActive] to forward
     * to the next [ChannelInboundHandler] in the [ChannelPipeline].
     *
     * Sub-classes may override this method to change behavior.
     */
    override fun channelActive(ctx: ChannelHandlerContext?) {
        lgr.info("收到连接: ${ctx!!.channel().remoteAddress()}")
    }

    /**
     * Calls [ChannelHandlerContext.fireExceptionCaught] to forward
     * to the next [ChannelHandler] in the [ChannelPipeline].
     *
     * Sub-classes may override this method to change behavior.
     */
    override fun exceptionCaught(ctx: ChannelHandlerContext?, cause: Throwable?) {
        lgr.error("JnrqTcpHandler failed by ${cause?.message}", cause)
        ctx?.close()
    }

    companion object {
        private val lgr = LoggerFactory.getLogger(JnrqTcpHandler::class.java)

//        private val listJsonFile = "rtu-meter.json"

    }
}